﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using JetBlack.Diagnostics;
using JetBlack.SimpleTopicBus.Messages;

namespace JetBlack.SimpleTopicBus.Distributor
{
    /// <summary>
    /// The Market brings brings together the managers and the interactors.
    /// </summary>
    internal class Market
    {
        private readonly List<Interactor> interactors = new List<Interactor>();
        private readonly SubscriptionManager subscriptionManager = new SubscriptionManager();
        private readonly NotificationManager notificationManager = new NotificationManager();
        private readonly PublisherManager publisherManager = new PublisherManager();

        internal Market()
        {
            subscriptionManager.OnSubscription += HandleForwardedSubscription;
            subscriptionManager.OnSubscriberMessage += HandleSubscriberMessage;
            subscriptionManager.OnClientMessage += HandleClientMessage;
            notificationManager.OnNewNotification += HandleNewNotification;
            subscriptionManager.OnStaleTopic += HandleStaleTopic;
            publisherManager.OnStalePublisher += HandleStalePublisher;
        }

        /// <summary>
        /// This method handles new notification requests raised by the NotificationManager.
        /// </summary>
        /// <param name="topicRegex">The regex pattern of the topic on which notification is requested.</param>
        /// <param name="notifiable">The notifiable interactor.</param>
        internal void HandleNewNotification(Regex topicRegex, Interactor notifiable)
        {
            lock (subscriptionManager)
                subscriptionManager.HandleNewNotificationRequest(topicRegex, notifiable);
        }

        /// <summary>
        /// This method handles all messages sent by the client interator.
        /// </summary>
        /// <param name="sender">The interactor that sent the message.</param>
        /// <param name="message">The message sent by the client.</param>
        internal void HandleMessage(Interactor sender, Message message)
        {
            switch (message.MessageType)
            {
                case MessageType.SubscriberMessage:
                    HandlePublishSubscriberMessage(message as SubscriberMessage, sender);
                    break;
                case MessageType.ClientMessage:
                    HandleSendClientData(message as ClientMessage, sender);
                    break;
                case MessageType.NotificationRequest:
                    HandleNotificationRequest(message as NotificationRequest, sender);
                    break;
                case MessageType.SubscriptionRequest:
                    HandleSubscription(message as SubscriptionRequest, sender);
                    break;
                default:
                    throw new ArgumentException("invalid message type");
            }
        }

        /// <summary>
        /// Adds a client to the list of current interactors.
        /// </summary>
        /// <param name="client">The client to add.</param>
        internal void AddClient(Interactor client)
        {
            lock (interactors)
                interactors.Add(client);
        }

        /// <summary>
        /// Removes a client from the list of interactors.
        /// </summary>
        /// <param name="client">The client interator to be removed.</param>
        internal void RemoveClient(Interactor client)
        {
            lock (publisherManager)
                publisherManager.Remove(client);

            lock (subscriptionManager)
                subscriptionManager.Remove(client);

            lock (notificationManager)
                notificationManager.RemoveInteractor(client);

            lock (interactors)
                interactors.Remove(client);
        }

        private void HandleSubscription(SubscriptionRequest message, Interactor subscriber)
        {
            lock (subscriptionManager)
                subscriptionManager.HandleSubscription(message, subscriber);
        }

        private void HandleNotificationRequest(NotificationRequest message, Interactor notifiable)
        {
            lock (notificationManager)
                notificationManager.HandleRequest(message, notifiable);
        }

        private void HandlePublishSubscriberMessage(SubscriberMessage message, Interactor publisher)
        {
            lock (subscriptionManager)
                subscriptionManager.HandleSubscriberMessage(message, publisher);
        }

        private void HandleSendClientData(ClientMessage message, Interactor publisher)
        {
            lock (subscriptionManager)
                subscriptionManager.HandleClientMessage(message, publisher);
        }

        /// <summary>
        /// This forwards a subscription request from the subscription manager
        /// to the notification manager.
        /// 
        /// Any clients that have asked to be informed on a subscription which
        /// matches their pattern will be informed.
        /// will be
        /// </summary>
        /// <param name="message"></param>
        private void HandleForwardedSubscription(ForwardedSubscriptionRequest message)
        {
            lock (notificationManager)
                notificationManager.NotifyListeners(message);
        }

        private void HandleSubscriberMessage(Interactor publisher, Interactor subscriber, SubscriberMessage message)
        {
            lock (publisherManager)
                publisherManager.Publish(publisher, subscriber, message);
        }

        private void HandleClientMessage(Interactor publisher, Interactor subscriber, ClientMessage message)
        {
            lock (publisherManager)
                publisherManager.Publish(publisher, subscriber, message);
        }

        private void HandleStalePublisher(Interactor publisher, IEnumerable<string> unmanagedTopics)
        {
            lock (subscriptionManager)
                subscriptionManager.HandleStalePublisher(publisher, unmanagedTopics);
        }

        private void HandleStaleTopic(Interactor publisher, Interactor subscriber, string topic)
        {
            try
            {
                subscriber.SendMessage(new SubscriberMessage(topic, true, null));
            }
            catch (Exception ex)
            {
                AppEnvironment.TraceSource.Error("Failed to send a DataChange message for a stale topic", ex);
            }
        }
    }
}
